A Local Reasoning for Global Invariants, Part I: Region Logic
نویسندگان
چکیده
ion and information hiding. There is an inconsistency in the declarations of Fig. 1. Fields rt and lt appear in the frame condition, which is part of the public interface used by client code outside class Comp, but these fields are declared private . (That is, they are visible only within code of Comp —and they are an implementation detail, so not suitable to be specpublic.) There is an established way to avoid exposing internal fields in interfaces. A data group is a public field name designated as an abstraction of some private (or protected) fields [Leino 1998; Leino et al. 2002]. We revise our example by declaring private lt , rt : Comp in datagroup chldrn; We revise the frame condition to use self.chldrn in place of self.lt and self.rt . The final version is modifies chldrn, x .parent , ancestors(self)‘size, ancestors(self)‘desc (6) Within the scope of lt , rt , the definition of chldrn must be visible: in reasoning about the implementation of some method in Comp, we need to know that add interferes with lt , rt . Data groups are particularly useful in connection with subclassing, but this topic is beyond our scope. So too are mechanisms such as model fields for abstraction in preand postconditions (see Sec. 9). An extreme case of abstraction pertains to representation invariants, i.e., those that pertain entirely to internal state —and so can be hidden entirely. As an example, suppose we want client programs to have access to the size of a composite. Our declaration does not allow client code to refer to field size but there could be a public method getSize. It might have no specification beyond its suggestive name, or it might even be specified in terms of the recursive definition of the size of a tree. But the code would rely on field size, and thus 5Or diverges if b already has two children. ACM Journal Name, Vol. V, No. N, Article A, Publication date: January YYYY. November 15, 2012 A:9 on invariant ∀o : Comp · ok(o). This is an example of an invariant that pertains to state that should be encapsulated so clients cannot interfere with it. As articulated by Hoare [1972], the hiding of internal invariants creates a mismatch: the invariants should not appear at all in the public interface, but should be required as precondition and ensured as postcondition for the purpose of checking the implementation of add and any other method of Comp. The ellipses in the preand postcondition of add (Fig. 1) would then be read as empty or as the invariant. Hoare’s mismatch is sound insofar as encapsulation prevents clients from interfering with module internals. A popular concept for encapsulation of shared mutable objects is ownership, which imposes hierarchical structure on the heap and restricts updates (or access) to ensure that an invocation on some receiver o can only update locations in objects transitively owned by o (e.g., [Clarke and Drossopoulou 2002; Aldrich and Chambers 2004]). One facet of ownership is that it serves to encapsulate invariants that depend only on owned objects. Another facet is that ownership provides a kind of implicit frame condition for clients: the owner is always allowed to update its owned objects, so that the owned objects need not appear in the frame condition. However, ownership is not enough as witnessed by the composite pattern. The composite pattern has been raised as a specification challenge [Leavens et al. 2007; Robby et al 2008] because, despite the hierarchical structure of objects, it does not fit ownership: clients may have references to internals. For example, a client program could have variables b, c of type Comp, with c a descendant of b. Ownership systems disallow this, or prevent the client from doing useful things with c. One objective of our work is to provide sound and flexible means to hide internal invariants at convenient granularities including but not limited to those for which ownership is suited. Part II of this paper develops a “second order frame rule” that accounts for the hiding of an invariant within a module, where a module consists of one or more classes. This account uses a straightforward notion of hypothetical correctness: a command is verified under assumptions that comprise some procedure specifications. The form of our second order frame rule, adapted from the “hypothetical frame rule” introduced by O’Hearn et al. [2009], caters for a single invariant formula such as the global invariants discussed above. The key novelty is to include, in a module interface, a kind of frame condition which we call the dynamic boundary , that designates the encapsulation boundary within which invariants are hidden. In Part I of this paper we lay the groundwork by formalizing in region logic the key ingredients of framing, namely, regions, separation, and frame conditions. A logic with stateful frame conditions. Elsewhere we present experiments with this approach to framing and invariants, including verification of the Composite implementation and non-trivial clients using an automated verifier based on axiomatic semantics [Banerjee et al. 2008; Rosenberg et al. 2010; Rosenberg 2011]. Here, our goal is to investigate the approach by formalizing it as a Hoare logic. Verification tools are typically compositional at the level of methods and modules. For a clear understanding of compositionality of effects in sequenced commands as well as in mismatched method specifications, a logic is an appropriate formalization. Within commands, verifiers are often based on verification conditions [Floyd 1967] —weakest preconditions or symbolic execution— rather than directly implementing compositional proof rules. However, such rules bring to light the technical issues, disentangled from issues such as heap model and avoidance of redundancy in weakest precondition formulas. One pleasant feature of compositionality at the level of commands is that, for atomic commands, we can use “small axioms” [O’Hearn et al. 2001] that focus on the local part of the state. For example, the axiom for field update can be instantiated for the command p.size : = p.size + c.size as follows: { p 6= null ∧ y = p.size + c.size } p.size : = p.size + c.size { p.size = y } [wr p.size] ACM Journal Name, Vol. V, No. N, Article A, Publication date: January YYYY. A:10 A. Banerjee, D. Naumann and S. Rosenberg Variable y serves to snapshot an initial value. Note that for brevity the frame condition is written in square brackets. The tag, wr , indicates a write effect; the tag is often omitted, as writes are the most oft-occurring effect. Our logic includes a so-called “frame” rule inspired by that of separation logic [Reynolds 2002; O’Hearn et al. 2001]. The purpose is to conjoin a formula to both preand postcondition, if the command cannot interfere with the value of the formula. For example, a procedure call rule yields {Q } b.add(c) {Q ′ } [ε] where ε is the frame condition and Q ,Q ′ the preand postcondition given by the specification, all instantiated for b and c. In particular, from (6), ε is the list of write effects b.chldrn, c.parent , ancestors(b)‘size, ancestors(b)‘desc In a proof, we might choose to strengthen Q to some P that implies d is not in the ancestors of b. From {P } b.add(c) {Q ′ } [ε] we want to derive the following, by framing the formula d .size = 10. {P ∧ d .size = 10 } b.add(c) {Q ′ ∧ d .size = 10 } [ε] (7) This is given by our frame rule, with two provisos written P ` δ frm (d .size = 10) and P ∧ d .size = 10⇒ δ ·/. ε, where δ is the read effect rd d , rd d .size. The “framing judgment” P ` δ frm d .size = 10 says that in P -states, the value of formula d .size = 10 depends only on the values of d and d .size. Here d .size is a sugared form of {d}‘size. The operator ·/. generates a conjunction of region disjointness formulas, given a list of read effects and a list of write effects. In this example the separator δ ·/. ε compares the relevant read effect ancestors(b)‘size and the relevant write effect d .size. The comparison yields the single disjointness formula, {d} # ancestors(b), which is equivalent to d / ∈ ancestors(b). (The operator ·/. is defined in Sec. 6.2.) As another example, suppose some method in class Comp needs to swap two non-null children, say using temporary t in the command C =̂ t : = lt ; lt : = rt ; rt : = t . We want to show that C maintains the invariant, i.e., this judgment: { ∀o : Comp · ok(o) } C { ∀o : Comp · ok(o) } [t , self.lt , self.rt ] By reasoning about assignments (proof rules in Fig. 16) and the definition of ok(o) we can show { ok(self) } C { ok(self) } [t , self.lt , self.rt ] (8) Now we use the frame rule to conjoin to (8) ∀o : Comp · o 6= self ⇒ ok(o), which yields preand postcondition ok(self) ∧ (∀o : Comp · o 6= self ⇒ ok(o)) which simplifies to ∀o : Comp · ok(o). (Formally, we use the rule of consequence for that simplification.) To use the frame rule we find read effects that frame the formula ∀o : Comp · o 6= self ⇒ ok(o). These are as follows (using \ for set subtraction): rd alloc, rd self, rd (alloc \ {self})‘rt , rd (alloc \ {self})‘lt , rd (alloc \ {self})‘size, rd (alloc \ {self})‘lt‘size, rd (alloc \ {self})‘rt‘size (9) (In Sec. 6 we show how the read effects are obtained.) Next, we must show the separation condition. Note that there are no variables in common between the write effects and the read effects. However, fields lt , rt occur in both. (Field size is read but not written.) Hence the separation condition is {self}# (alloc \ {self}) which is equivalent to self / ∈ (alloc \ {self}). ACM Journal Name, Vol. V, No. N, Article A, Publication date: January YYYY. November 15, 2012 A:11 x , y , r ∈ VarName f , g ∈ FieldName K ∈ DeclaredClassNames (Types) T ::= int | rgn | K (Program Expressions) E ::= x | c | null | E ⊕ E where c is in Z and ⊕ is in {=,+,−, ∗, >, . . .} (Region Expressions) G ::= ∅ | x | {E} | G‘f | G/K | G ⊗G where ⊗ is in {∪,∩, \} (Expressions) F ::= E | G (Commands) C ::= skip | x : = F | x : = new K | x : = x .f | x .f : = F | if E then C else C | while E do C | C ;C | var x :T in C Fig. 2. Programming language. We confuse category names with typical elements (e.g., E). The atomic commands are skip and the various forms of assignment. To emphasize this reasoning idiom we sketch another example. Our music editing application may have a number of windows open at once. Consider an editing operation that applies to audio setups. The application maintains invariant ∀w : AudioWindow · valid(w) where valid imposes conditions on the audio setup and its rendering in the window. To reason about an operation, edit , one would focus on the body of edit , which updates fields of self and some other objects. These would falsify valid(self) and then for non-trivial reasons restore it. Having established that the body of edit preserves valid(self), we would frame the formula ∀w : AudioWindow · w = self∨valid(w) and then check that it is separate from the writes of edit . An interesting but annoying feature of the logic is that stateful effects are subject to interference. This may explain why the simple approach we explore here had not been pursued long before the work of Kassios [2011]. Consider this sub-sequence in an unfolding of the loop body of add : p : = p.parent ; p.size : = p.size + x .size; (10) What is the effect? The effect of the assignment to p.size, in isolation, can be expressed by wr p.size, because the initial value of p is indeed the object whose size is updated. But for the displayed sequence, it is not the initial value of p but rather of p.parent whose size gets updated. In our terminology, the effect wr p.size is not immune from the effect, wr p, of the first assignment. The proof rule for sequencing combines effects of the commands in sequence, but imposes an immunity condition on them. To verify the displayed sequence, we first use a rule of state-dependent effect subsumption, ascribing to p.size : = p.size + x .size the effect wr ancestors(self)‘size which is sound owing to an invariant of the loop: p ∈ ancestors(self). The effect wr ancestors(self)‘size is immune from update of p. Immunity is the topic of Sec. 6.3. One way to implement these ideas in an automated verifier is to desugar frame conditions to their semantics as postconditions. Then nothing explicit needs to be done about immunity. The effect of assignments on expressions in frame conditions is calculated just like the effect of assignments on expressions in other formulas. In our experiments using an SMT solver we also found that the prover is able to handle quantified global invariants without assistance, at least those like ok that refer concretely to the state. On the other hand, in case of inductively defined predicates (encoded as uninterpreted functions plus axioms) and predicates expressed using procedural abstraction, some explicit framing hints are valuable [Rosenberg et al. 2010]. A hint generates a framing judgment as additional verification condition, as we discuss in Sec. 6 where we also provide a deductive system for framing judgments. ACM Journal Name, Vol. V, No. N, Article A, Publication date: January YYYY. A:12 A. Banerjee, D. Naumann and S. Rosenberg 3. PROGRAMMING LANGUAGE This section presents an illustrative language for which we formalize the programming logic.
منابع مشابه
A Local Reasoning for Global Invariants, Part II: Dynamic Boundaries
The hiding of internal invariants creates a mismatch between procedure specifications in an interface and proof obligations on the implementations of those procedures. The mismatch is sound if the invariants depend only on encapsulated state, but encapsulation is problematic in contemporary software due to the many uses of shared mutable objects. The mismatch is formalized here in a proof rule ...
متن کاملRegional Logic for Local Reasoning about Global Invariants
Shared mutable objects pose grave challenges in reasoning, especially for data abstraction and modularity. This paper presents a novel logic for erroravoiding partial correctness of programs featuring shared mutable objects. Using a first order assertion language, the logic provides heap-local reasoning about mutation and separation, via ghost fields and variables of type ‘region’ (finite sets ...
متن کاملLocal Reasoning and Dynamic Framing for the Composite Pattern and Its Clients
The Composite design pattern poses a challenge for reasoning about invariants with non-local dependencies. Region logic is a Hoare logic augmented with simple notations for object sets that can be used as dynamic frames in modifies clauses. Using region logic, this paper provides an elementary specification pattern for the Composite design pattern and evaluates the specification by using it in ...
متن کاملLocal Reasoning about While-Loops
Separation logic is an extension of Hoare logic that allows local reasoning. Local reasoning is a powerful feature that often allows simpler specifications and proofs. However, this power is not used to reason about while-loops. In this paper an inference rule is presented that allows using local reasoning to verify the partial correctness of while-loops. Instead of loop invariants this inferen...
متن کاملIvy: Interactive Verification of Parameterized Systems via Effectively Propositional Reasoning
The design and implementation of parametric systems can be very tricky even for experienced researchers. We describe an interactive system — Ivy — for interactively verifying parameterized systems. Ivy is based on the following principles: (i) Ivy first attempts to locate counterexamples by bounding the number of protocol actions and symbolically searching for (unbounded) bad inputs. (ii) Invar...
متن کاملLocal Reasoning for Termination
In this paper, we bridge the gap between separation logic and transition invariants in order to obtain a uniform framework for proving total correctness of pointer programs. We introduce the concept of separated transition constraints to describe the local effect of pointer programs. Separated transition constraints provide a new view on locality by their non-tight interpretation. Furthermore, ...
متن کامل